﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PDO.DataPersistence.Core
{

    /// <summary>
    /// Tokenizes expression into argument1 / operator / argument2 elements.
    /// </summary>
    public class ConditionalTokenizer
    {
        private bool _chaining;
        private string _operator;
        private string _arg1;
        private string _arg2;

        private string _propertyPath;
        private int _position;
        private bool _inQuote;
        private int _parenLevel;

        StringBuilder _buffer;

        public ConditionalTokenizer(string propertyPath)
        {
            _propertyPath = propertyPath;
            _position = 0;
        }

        public bool IsComplete
        {
            get
            {
                return string.IsNullOrEmpty(_arg2) && (_position >= _propertyPath.Length);
            }
        }

        public string Operator
        {
            get { return _operator; }
        }

        public string Argument1
        {
            get { return _arg1; }
        }

        public string Argument2
        {
            get { return _arg2; }
        }


        private bool unloadBuffer()
        {
            bool exitParser = false;

            if (_chaining == false && string.IsNullOrEmpty(_arg1))
            {
                _arg1 = _buffer.ToString();
                _buffer.Length = 0;
            }
            else if (string.IsNullOrEmpty(_operator))
            {
                _operator = _buffer.ToString();
                _buffer.Length = 0;
            }
            else if (string.IsNullOrEmpty(_arg2))
            {
                _arg2 = _buffer.ToString();
                _buffer.Length = 0;
                exitParser = true;
            }
            return exitParser;
        }

        public bool MoveNext()
        {

            _operator = null;
            _arg1 = null;
            _arg2 = null;

            if (_position >= _propertyPath.Length)
            {
                return false;
            }

            char c;

            _buffer = new StringBuilder();
            _inQuote = false;
            _parenLevel = 0;


            bool exitParser = false;
            int startPosition = _position;
            while (_position < _propertyPath.Length)
            {
                c = _propertyPath[_position];
                switch (c)
                {
                    case ' ':
                    case '\t':
                        // skip whitespace unless in quote
                        if (_inQuote)
                        {
                            _buffer.Append(c);
                        }
                        break;

                    case '|':
                    case '&':
                        if (_parenLevel > 0)
                        {
                            _buffer.Append(c);
                        }
                        else
                        {
                            exitParser = unloadBuffer();

                            _operator = c.ToString();

                            // Allow boolean operator syntax with either single or double "&" and "|".
                            // E.g. "A && B" or "A & B", "C || D" or "C | D"
                            if (_propertyPath[_position + 1] == c)
                            {
                                _position++;
                            }
                        }

                        break;

                    case '\"':
                        if (_inQuote)
                        {
                            _inQuote = false;
                        }
                        else
                        {
                            _inQuote = true;
                        }

                        _buffer.Append(c);

                        break;


                    case '(':
                        if (_inQuote)
                        {
                            _buffer.Append(c);
                        }
                        else
                        {
                            if (_parenLevel > 0)
                            {
                                _buffer.Append(c);
                            }

                            _parenLevel++;
                        }
                        break;

                    case ')':
                        if (_inQuote)
                        {
                            _buffer.Append(c);
                        }
                        else
                        {
                            if (_parenLevel < 1)
                            {
                                throw new InvalidOperationException("Illegal ) found; no matching opening parenthese.");
                            }

                            if (_parenLevel > 1)
                            {
                                _buffer.Append(c);
                            }
                            _parenLevel--;
                        }
                        break;

                    default:
                        _buffer.Append(c);
                        break;
                }

                _position++;

                if (exitParser)
                {
                    break;
                }
            }


            if (_inQuote)
            {
                throw new InvalidOperationException("No closing quote.");
            }
            else if (_parenLevel > 0)
            {
                throw new InvalidOperationException("No closing parentheses.");
            }

            if (_buffer.Length > 0)
            {
                unloadBuffer();
            }

            _position++;
            _chaining = true;

            return true;
        }
    }

}
